package listener

import (
	"encoding/json"
	"fmt"
	"github.com/ahmetb/go-linq/v3"
	"github.com/sirupsen/logrus"
	"hcy-api/lib/id"
	"hcy-api/lib/utils"
	"hcy-api/structs/enum"
	"hcy-api/structs/idp"
	"hcy-api/structs/tables"
	"idp/global"
	"idp/repository"
	"strings"
	"time"
)

var IdpHandler = new(idpHandler)

type idpHandler struct {
}

// GetDeviceId 处理举手消息
func (s *idpHandler) GetDeviceId(msg idp.HandsUpMsgForVi) (deviceID int64, isNew bool, err error) {
	var (
		li []tables.IdpClient
	)
	// 1. 查询数据库，确认设备是否已经被收录，如果没有就插入，如果有就直接返回设备ID
	li, err = repository.ClientRepository.FindProbableList(msg.BiosSerial, msg.MainBoardSerial, msg.HardDiskSerial, msg.ActiveMAC, msg.Hostname)
	if err != nil {
		logrus.Errorf("[ err: %v] = [HandsUpMsg :%v]", err, msg)
		return
	}
	if len(li) == 0 {
		isNew = true
		item := tables.IdpClient{
			Id:              id.GetSnowId(),
			BiosSerial:      msg.BiosSerial,
			MainBoardSerial: msg.MainBoardSerial,
			HardDiskSerial:  msg.HardDiskSerial,
			ActiveMAC:       msg.ActiveMAC,
			IP:              msg.IP,
			Hostname:        msg.Hostname,
			CreatedAt:       time.Now().Unix(),
			IsOverlap:       false,
		}
		err = repository.ClientRepository.Save(item)
		if err != nil {
			logrus.Errorf("[err :%v] = [argus:%v]", err, item)
			return
		}
		return item.Id, isNew, nil
	}
	// 如果已经存在，就需要检查是否存在重复
	if len(li) == 1 {
		return li[0].Id, isNew, nil
	}
	uli := make([]tables.IdpClient, 0)
	for _, item := range li {
		score := 0
		if msg.BiosSerial == item.BiosSerial {
			score++
		}
		if msg.MainBoardSerial == item.MainBoardSerial {
			score++
		}
		if msg.HardDiskSerial == item.HardDiskSerial {
			score++
		}
		if msg.Hostname == item.Hostname {
			score++
		}
		if msg.ActiveMAC == item.ActiveMAC {
			score++
		}
		//如果5项中有3项一样，就判定为原来的设备发生了更新，例如更换零件或者重装系统
		if score >= 3 {
			uli = append(uli, item)
		}
	}
	if len(uli) == 1 {
		return uli[0].Id, false, nil
	}
	// 长度超过1，说明可能存在采集信息相同的设备，需要管理员人为干预
	for _, item := range uli {
		if !item.IsOverlap {
			item.IsOverlap = true
			err = repository.ClientRepository.Save(item)
			if err != nil {
				logrus.Errorf("[err: %v] = [value:%v]", err, item)
			}
		}
	}
	// todo 这里应该设置一个错误任务队列，将意外情况汇报给管理员。
	return -1, false, nil
}

// HandleGuarderPipMsg  处理守卫进程管道中读取到的消息
func (s *idpHandler) HandleGuarderPipMsg(m idp.ClientResponse) {
	var (
		bi  idp.ResultMsg
		err error
	)
	err = json.Unmarshal([]byte(m.Content.Result), &bi)
	if err != nil {
		logrus.Errorf("不合法的 guarder pip 消息：%v", m)
		return
	}
	if pc, ok := global.InfoCatchMaoForIdp.Load(m.DeviceId); ok {
		switch bi.MsgType {
		case "cpu":
			var c idp.CpuMsg
			if err = json.Unmarshal([]byte(bi.Result), &c); err != nil {
				logrus.Errorf("不合法的 guarder pip 消息：%v", err)
				return
			}
			pc.(*global.IdpCache).LastCpuWarn = c
			if err = repository.WinWarnRepository.SaveLoadWarn(tables.IdpWarnLoad{
				Id:        id.GetSnowId(),
				DeviceId:  m.DeviceId,
				LoadType:  "cpu",
				CreatedAt: time.Now().Unix(),
				Content:   bi.Result,
				Sample:    c.Sample,
			}); err != nil {
				logrus.Error(err)
				return
			}
		case "memory":
			var c idp.MemoryMsg
			if err = json.Unmarshal([]byte(bi.Result), &c); err != nil {
				logrus.Error(err)
				return
			}
			pc.(*global.IdpCache).LastMemoryWarn = c
			if err = repository.WinWarnRepository.SaveLoadWarn(tables.IdpWarnLoad{
				Id:        id.GetSnowId(),
				DeviceId:  m.DeviceId,
				LoadType:  "memory",
				CreatedAt: time.Now().Unix(),
				Content:   bi.Result,
				Sample:    c.Sample,
			}); err != nil {
				logrus.Error(err)
				return
			}
		case "session":
			var c idp.WinSessionChangeMsg
			if err = json.Unmarshal([]byte(bi.Result), &c); err != nil {
				logrus.Error(err)
				return
			}
			pc.(*global.IdpCache).LastWinSession = c
			if err = repository.WinWarnRepository.SaveHostnameChange(tables.IdpWarnHostname{
				Id:        id.GetSnowId(),
				DeviceId:  m.DeviceId,
				CreatedAt: time.Now().Unix(),
				Content:   bi.Result,
				Hostname:  c.Username,
				Describe:  c.Describe,
			}); err != nil {
				logrus.Error(err)
				return
			}
		default:
			logrus.Error("unknown guarder msg:", m)
		}
	}
}

// HandleCliMsg 处理插件消息
func (s *idpHandler) HandleCliMsg(m idp.ClientResponse) {
	switch m.Content.Plugin {
	case "cpu.exe":
		s.cpu(m.DeviceId, m.Content)
	case "memory.exe":
		s.memory(m.DeviceId, m.Content)
	case "disk.exe":
		s.disk(m.DeviceId, m.Content)
	case "board.exe":
		s.board(m.DeviceId, m.Content)
	case "abstract.exe":
		s.sysAbstract(m.DeviceId, m.Content)
	case "vap.exe":
		s.app(m.DeviceId, m.Content)
	case "file.exe":
		s.checkFile(m.DeviceId, m.Content)
	case "process.exe":
		s.checkProcess(m.DeviceId, m.Content)
	default:
		logrus.Error("未知插件", m.Content)
	}
}

func (s *idpHandler) cpu(clientId int64, pluginResult idp.PluginResult) {
	args := ""
	for i := range pluginResult.Args {
		args += pluginResult.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "":
		var r idp.RCpu
		_ = json.Unmarshal([]byte(pluginResult.Result), &r)
		if len(r) > 0 {
			if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
				obj := v.(*global.IdpCache)
				if obj.CPU.Name != r[0].Name || obj.CPU.ProcessorId != r[0].ProcessorID {
					l := tables.IdpLogCpu{
						IdpLogBase: tables.IdpLogBase{
							Id:        id.GetSnowId(),
							ClientId:  clientId,
							CreatedAt: time.Now().Unix(),
							Content:   pluginResult.Result,
						},
						Name:        r[0].Name,
						ProcessorId: r[0].ProcessorID,
					}
					obj.CPU = l
					if err := repository.WinLogRepository.SaveLogCpu(&l); err != nil {
						logrus.Error(err)
						return
					}
				}

			}
		}
	case "-id":
		logrus.Debug(pluginResult.Result)
	default:
		logrus.Error("未知的cpu 参数")
	}
}

func (s *idpHandler) memory(clientId int64, pluginResult idp.PluginResult) {
	args := ""
	for i := range pluginResult.Args {
		args += pluginResult.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "":
		var r idp.RMemory
		_ = json.Unmarshal([]byte(pluginResult.Result), &r)
		var (
			details   []string
			totalSize int64
		)
		for i := range r {
			details = append(details, r[i].SerialNumber)
			totalSize += r[i].Capacity
		}
		bs, _ := json.Marshal(details)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			if obj.Memory.Details != string(bs) ||
				obj.Memory.Num != len(r) ||
				obj.Memory.TotalSize != totalSize {
				l := tables.IdpLogMemory{
					IdpLogBase: tables.IdpLogBase{
						Id:        id.GetSnowId(),
						ClientId:  clientId,
						CreatedAt: time.Now().Unix(),
						Content:   pluginResult.Result,
					},
					TotalSize: totalSize,
					Num:       len(r),
					Details:   string(bs),
				}
				obj.Memory = l
				if err := repository.WinLogRepository.SaveLogMemory(&l); err != nil {
					logrus.Error(err)
					return
				}
			}
		}
	case "-serial":
		logrus.Debug(pluginResult.Result)
	default:
		logrus.Debug("未知的memory参数")
	}
}

func (s *idpHandler) disk(clientId int64, pr idp.PluginResult) {
	args := ""
	for i := range pr.Args {
		args += pr.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "-type=physics":
		var r idp.RPhysicsDisk
		_ = json.Unmarshal([]byte(pr.Result), &r)
		var (
			details   []string
			totalSize int64
		)
		for i := range r {
			details = append(details, r[i].SerialNumber)
			totalSize += r[i].Size
		}
		bs, _ := json.Marshal(details)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			if obj.DiskPhysics.Details != string(bs) ||
				obj.DiskPhysics.Num != len(r) ||
				obj.DiskPhysics.TotalSize != totalSize {
				l := tables.IdpLogDisk{
					IdpLogBase: tables.IdpLogBase{
						Id:        id.GetSnowId(),
						ClientId:  clientId,
						CreatedAt: time.Now().Unix(),
						Content:   pr.Result,
					},
					TotalSize: totalSize,
					Num:       len(r),
					Details:   string(bs),
				}
				obj.DiskPhysics = l
				if err := repository.WinLogRepository.SaveLogDisk(&l); err != nil {
					logrus.Error(err)
					return
				}
			}
		}

	case "-type=logic":
		var r idp.RLogicDisk
		_ = json.Unmarshal([]byte(pr.Result), &r)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			v.(*global.IdpCache).DiskLogic = r
		}
	case "-type=physics -s":
		logrus.Debug(pr.Result)
	case "-type=logic -s":
		logrus.Debug(pr.Result)
	default:
		logrus.Debug("未知的disk参数")
	}

}

func (s *idpHandler) board(clientId int64, pr idp.PluginResult) {
	args := ""
	for i := range pr.Args {
		args += pr.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "-type=base":
		var r idp.RBoardBase
		_ = json.Unmarshal([]byte(pr.Result), &r)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			if obj.BoardBase.Serial != r.BoardSerialNumber {
				l := tables.IdpLogBoard{
					IdpLogBase: tables.IdpLogBase{
						Id:        id.GetSnowId(),
						ClientId:  clientId,
						CreatedAt: time.Now().Unix(),
						Content:   pr.Result,
					},
					Serial: r.BoardSerialNumber,
				}
				obj.BoardBase = l
				if err := repository.WinLogRepository.SaveLogBoard(&l); err != nil {
					logrus.Error(err)
					return
				}
			}
		}
	case "-type=bios":
		var r idp.RBios
		_ = json.Unmarshal([]byte(pr.Result), &r)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			obj.BoardBios = r
		}
	case "-type=board":
		var r idp.RMainBoard
		_ = json.Unmarshal([]byte(pr.Result), &r)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			obj.BoardMain = r
		}
	case "-type=board -s":
		logrus.Debug(pr)
	case "-type=bios -s":
		logrus.Debug(pr)
	case "-type=base -s":
		logrus.Debug(pr)
	default:
		logrus.Error("未知的board参数")
	}
}

func (s *idpHandler) sysAbstract(clientId int64, pr idp.PluginResult) {
	args := ""
	for i := range pr.Args {
		args += pr.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "":
		var r idp.RSysAbstract
		_ = json.Unmarshal([]byte(pr.Result), &r)
		if v, ok := global.InfoCatchMaoForIdp.Load(clientId); ok {
			obj := v.(*global.IdpCache)
			if obj.SysAbstract.Caption != r.Caption ||
				obj.SysAbstract.Version != r.Version ||
				obj.SysAbstract.SerialNumber != r.SerialNumber {
				l := tables.IdpLogSysAbstract{
					IdpLogBase: tables.IdpLogBase{
						Id:        id.GetSnowId(),
						ClientId:  clientId,
						CreatedAt: time.Now().Unix(),
						Content:   pr.Result,
					},
					Caption:      r.Caption,
					Version:      r.Version,
					SerialNumber: r.SerialNumber,
				}
				obj.SysAbstract = l
				if err := repository.WinLogRepository.SaveLogSysAbstract(&l); err != nil {
					logrus.Error(err)
					return
				}
			}

		}

	default:
		logrus.Debug("未知的sys_abstract参数")

	}
}

func (s *idpHandler) app(clientId int64, pr idp.PluginResult) {
	args := ""
	for i := range pr.Args {
		args += pr.Args[i]
		args += " "
	}
	switch strings.TrimSpace(args) {
	case "-al":
		fallthrough
	case "-appList":
		oldLog, err := repository.WinLogRepository.FindLastApp(clientId)
		if err != nil {
			logrus.Error(err)
			return
		}
		// 采集到的数据和数据库保存的数据不一致时需要写入新记录
		if oldLog.Content != pr.Result {
			l := tables.IdpLogApp{
				IdpLogBase: tables.IdpLogBase{
					Id:        id.GetSnowId(),
					ClientId:  clientId,
					CreatedAt: time.Now().Unix(),
					Content:   pr.Result,
				},
			}
			if err = repository.WinLogRepository.SaveLogApp(&l); err != nil {
				logrus.Error(err)
			}
		}
		// 检查变化的部分，写入 app_change_log
		s.appChangelog(clientId, pr, oldLog)
	}
}

func (s *idpHandler) appChangelog(deviceId int64, pr idp.PluginResult, oldLog tables.IdpLogApp) {
	var (
		changes []tables.IdpAppChangeLog
		newLi   idp.RApps
		oldLi   idp.RApps
	)
	_ = json.Unmarshal([]byte(pr.Result), &newLi)
	_ = json.Unmarshal([]byte(oldLog.Content), &oldLi)
	if len(newLi) > 0 && len(oldLi) == 0 {
		for _, v := range newLi {
			changes = append(changes, tables.IdpAppChangeLog{
				Id:              id.GetSnowId(),
				ClientId:        deviceId,
				CreatedAt:       time.Now().Unix(),
				DisplayName:     v.DisplayName,
				DisplayVersion:  v.DisplayVersion,
				Publisher:       v.Publisher,
				InstallDate:     v.InstallDate,
				InstallLocation: v.InstallLocation,
				Change:          "初始记录",
			})
		}
	} else {
		for _, v := range newLi {
			if !utils.Contains(oldLi, v) {
				changes = append(changes, tables.IdpAppChangeLog{
					Id:              id.GetSnowId(),
					ClientId:        deviceId,
					CreatedAt:       time.Now().Unix(),
					DisplayName:     v.DisplayName,
					DisplayVersion:  v.DisplayVersion,
					Publisher:       v.Publisher,
					InstallDate:     v.InstallDate,
					InstallLocation: v.InstallLocation,
					Change:          "新增",
				})
			}
		}
		for _, v := range oldLi {
			if !utils.Contains(newLi, v) {
				changes = append(changes, tables.IdpAppChangeLog{
					Id:              id.GetSnowId(),
					ClientId:        deviceId,
					CreatedAt:       time.Now().Unix(),
					DisplayName:     v.DisplayName,
					DisplayVersion:  v.DisplayVersion,
					Publisher:       v.Publisher,
					InstallDate:     v.InstallDate,
					InstallLocation: v.InstallLocation,
					Change:          "移除",
				})
			}
		}
	}
	if len(changes) > 0 {
		if err := repository.WinLogRepository.SaveAppChangeLog(changes); err != nil {
			logrus.Error(err)
			return
		}
	}

}

// checkFile 和 checkProcess 逻辑是一样的，都是先检查是否为指令调用，如果是就检查是否已经记录过 ok的结果
func (s *idpHandler) checkFile(clientId int64, pr idp.PluginResult) {
	// 只有任务会调用这个指令，或者用户会实时调用
	if len(pr.Args) > 0 && strings.HasPrefix(pr.Args[0], "-taskId") {
		rfile := new(idp.RFile)
		if err := json.Unmarshal([]byte(pr.Result), rfile); err != nil {
			logrus.Error(err)
			return
		}
		li, err := repository.TaskRepository.FindTaskResult(rfile.TaskId, clientId)
		if err != nil {
			logrus.Error(err)
			return
		}
		// 如果没有记录就写入记录
		if len(li) == 0 {
			l := &tables.IdpTaskResult{
				Id:        id.GetSnowId(),
				TaskId:    rfile.TaskId,
				ClientId:  clientId,
				Details:   pr.Result,
				CreatedAt: time.Now().Unix(),
			}
			if rfile.IsExist {
				l.IsOk = tables.IdpTaskResultForOk
			} else {
				l.IsOk = tables.IdpTaskResultForNo
			}
			if err = repository.TaskRepository.CreateTaskResult(l); err != nil {
				logrus.Error(err)
				return
			}
			if rfile.IsExist {
				RemoveCatchInstruct(clientId, idp.PluginInstruct{
					Cmd:  pr.Plugin,
					Args: pr.Args,
				})
			}
		} else {
			// 有记录就检查记录中是否存在已经OK的记录，
			if linq.From(li).Contains(func(v interface{}) bool {
				return v.(tables.IdpTaskResult).IsOk == tables.IdpTaskResultForOk
			}) {
				//有就移除检查指令
				RemoveCatchInstruct(clientId, idp.PluginInstruct{
					Cmd:  pr.Plugin,
					Args: pr.Args,
				})
			} else {
				// 如果是完成就写入信息
				if rfile.IsExist {
					l := &tables.IdpTaskResult{
						Id:        id.GetSnowId(),
						TaskId:    rfile.TaskId,
						ClientId:  clientId,
						Details:   pr.Result,
						CreatedAt: time.Now().Unix(),
						IsOk:      tables.IdpTaskResultForOk,
					}
					if err = repository.TaskRepository.CreateTaskResult(l); err != nil {
						logrus.Error(err)
						return
					}
				} else {
					// 不是完成就检查上一条数据的时间和当前时间是否相差1天，相差一天就写入记录
					if li[0].CreatedAt+enum.OneDay < time.Now().Unix() {
						l := &tables.IdpTaskResult{
							Id:        id.GetSnowId(),
							TaskId:    rfile.TaskId,
							ClientId:  clientId,
							Details:   pr.Result,
							CreatedAt: time.Now().Unix(),
							IsOk:      tables.IdpTaskResultForNo,
						}
						if err = repository.TaskRepository.CreateTaskResult(l); err != nil {
							logrus.Error(err)
							return
						}
					}
				}
			}
		}
	} else {
		fmt.Println("用户实时调用")
	}
}

// checkFile 和 checkProcess 逻辑是一样的，都是先检查是否为指令调用，如果是就检查是否已经记录过 ok的结果
func (s *idpHandler) checkProcess(clientId int64, pr idp.PluginResult) {
	if len(pr.Args) > 0 && strings.HasPrefix(pr.Args[0], "-taskId") {
		rp := new(idp.RProcess)
		if err := json.Unmarshal([]byte(pr.Result), rp); err != nil {
			logrus.Error(err)
			return
		}
		li, err := repository.TaskRepository.FindTaskResult(rp.TaskId, clientId)
		if err != nil {
			logrus.Error(err)
			return
		}
		// 如果没有记录就写入记录
		if len(li) == 0 {
			l := &tables.IdpTaskResult{
				Id:        id.GetSnowId(),
				TaskId:    rp.TaskId,
				ClientId:  clientId,
				Details:   pr.Result,
				CreatedAt: time.Now().Unix(),
			}
			if rp.IsExist {
				l.IsOk = tables.IdpTaskResultForOk
			} else {
				l.IsOk = tables.IdpTaskResultForNo
			}
			if err = repository.TaskRepository.CreateTaskResult(l); err != nil {
				logrus.Error(err)
				return
			}
			if rp.IsExist {
				RemoveCatchInstruct(clientId, idp.PluginInstruct{
					Cmd:  pr.Plugin,
					Args: pr.Args,
				})
			}
		} else {
			// 有记录就检查记录中是否存在已经OK的记录，
			if linq.From(li).Contains(func(v interface{}) bool {
				return v.(tables.IdpTaskResult).IsOk == tables.IdpTaskResultForOk
			}) {
				//有就移除检查指令
				RemoveCatchInstruct(clientId, idp.PluginInstruct{
					Cmd:  pr.Plugin,
					Args: pr.Args,
				})
			} else {
				// 没有就检查最近8小时内是否写入过记录，没有写入过就写入记录
				if li[0].CreatedAt < time.Now().Unix()+enum.OneDay {
					l := &tables.IdpTaskResult{
						Id:        id.GetSnowId(),
						TaskId:    rp.TaskId,
						ClientId:  clientId,
						Details:   pr.Result,
						CreatedAt: time.Now().Unix(),
					}
					if err = repository.TaskRepository.CreateTaskResult(l); err != nil {
						logrus.Error(err)
						return
					}
				}
			}
		}
	} else {
		fmt.Println("用户实时调用")
	}
}
